package file;

import java.io.File ;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;

import user.LocalUser;
import user.User;

public class TransferFile {

	private User receiver, sender ;
	private int fileID ;
	long fileSize;
	private File file ;
	
	/**
	 * Create a new TransferFile with information specified
	 * 
	 * @param filename The name of the file that will be created on the local machine
	 * @param fileID The ID of the file (according to PropFile signal ID)
	 * @param fileSize The size of the file
	 * @param send The user that send the file
	 */
	public TransferFile (String filename, int fileID, long fileSize, User sender) {
		this.sender = sender ;
		this.receiver = LocalUser.getLocalUser() ;
		this.fileID = fileID ;
		this.fileSize = fileSize ;
		this.file = new File(filename) ;
	}
	
	/**
	 * Create a new TransferFile from a old one with the new filepath
	 * 
	 * @param tf The old TransferFile
	 * @param filename The new name of the file that will be created on the local machine
	 */
	public TransferFile (TransferFile tf, String filename) {
		this.sender = tf.sender ;
		this.receiver = tf.receiver ;
		this.fileID = tf.fileID ;
		this.fileSize = tf.fileSize ;
		this.file = new File(filename) ;
	}
	
	/**
	 * Create a new TransferFile with information specified
	 * 
	 * @param file The file on the local machine
	 * @param fileID The ID of the file (according to PropFile signal ID)
	 * @param remoteUser The user that receive the file
	 */
	public TransferFile (File file, int fileID, User receiver) {
		this.file = file ;
		this.receiver = receiver ;
		this.sender = LocalUser.getLocalUser() ;
		this.fileID = fileID ;
		this.fileSize = file.length() ;
	}
	
	/**
	 * @return The ID of the file
	 */
	public int getID () {
		return this.fileID ;
	}
	
	/**
	 * @return The sender of the file
	 */
	public User getSender () {
		return this.sender ;
	}
	
	/**
	 * @return The receiver of the file
	 */
	public User getReceiver () {
		return this.receiver ;
	}
	
	/**
	 * @return The name of the file
	 */
	public String getFileName () {
		return this.file.getName() ;
	}
	
	/**
	 * @return The absolute file path
	 */
	public String getAbsoluteFilePath () {
		return this.file.getAbsolutePath() ;
	}
	

	/**
	 * @return The file folder
	 */
	public String getFileFolder() {
		return this.file.getParent() ;
	}
	
	/**
	 * @return true if the TransferFile correspond to a file that will be send
	 */
	public boolean send () {
		return this.sender == LocalUser.getLocalUser() ;
	}
	
	/**
	 * @return The size of the file
	 */
	public long getSize () {
		return this.fileSize ;
	}
	
	/**
	 * Check if the 2 TransferFile correspond : Same sender, same receiver, same ID
	 * 
	 * @param others
	 * @return true if the 2 TransferFile are equals
	 */
	@Override
	public boolean equals (Object other) {
		if (other != null && other instanceof TransferFile) {
			TransferFile tf = (TransferFile)other ;
			return this.sender.equals(tf.sender) && this.receiver.equals(tf.receiver) && this.fileID == tf.fileID ;
		}
		else {
			return false ;
		}
	}
	
	/**
	 * Transfer the InputStream into the OutputStream and notify the TransferFileObserver
	 */
	private void transfer (InputStream is, OutputStream os, TransferFileObserver[] tfos) {
		for (TransferFileObserver tfo : tfos) {
			tfo.startFileTransfer (this) ;
		}
		int transfer = -1 ;
		long transfered = 0 ;
		try {
			while (transfered < this.fileSize) {
				os.write(is.read()) ;
				transfered ++ ;
				if ((int) (transfered / this.fileSize) != transfer) {
					for (TransferFileObserver tfo : tfos) {
						tfo.updateFileTransfer (this, (transfer = (int) (transfered / this.fileSize))) ;
					}
				}
			}
			for (TransferFileObserver tfo : tfos) {
				tfo.updateFileTransfer (this, 100) ;
				tfo.stopFileTransfer (this, true) ;
			}
			
		}
		catch (IOException e){
			for (TransferFileObserver tfo : tfos) {
				tfo.stopFileTransfer (this, false) ;
			}
		}
	}
	
	/**
	 * Write the file to the OutputStream specified. 
	 * 
	 * @param os
	 * @throws BadFileTypeException if an error occurs reading the file
	 */
	public void send (OutputStream os, TransferFileObserver[] tfos) throws BadFileTypeException{
		try {
			InputStream is = new FileInputStream(this.file) ;
			transfer (is, os, tfos) ;
			is.close() ;
		} 
		catch (FileNotFoundException e) {
			throw new BadFileTypeException() ;
		} 
		catch (IOException e) {
			for (TransferFileObserver tfo : tfos) {
				tfo.stopFileTransfer (this, false) ;
			}
		}
	}
	
	/**
	 * Read the file from the InputStream specified 
	 * 
	 * @param is
	 * @throws BadFileTypeException if an error occurs sending the file
	 */
	public void receive (InputStream is, TransferFileObserver[] tfos) throws BadFileTypeException {
		try {
			OutputStream os = new FileOutputStream(this.file) ;
			transfer (is, os, tfos) ;
			os.close() ;
		} 
		catch (FileNotFoundException e) {
			throw new BadFileTypeException() ;
		}
		catch (IOException e) {
			for (TransferFileObserver tfo : tfos) {
				tfo.stopFileTransfer (this, false) ;
			}
		}
	}
	
	/**
	 * @param sender The sender
	 * @param fileID The ID of the file
	 * @return A unique identifier for this object
	 */
	public static String createIdentifier (InetAddress sender, int ID) {
		return User.createIdentifier(sender) + String.valueOf(ID) ;
	}
	
	/**
	 * @return A unique identifier for this object
	 */
	public String getIdentifier () {
		return createIdentifier(this.sender.getAddress(), this.fileID) ;
	}

}
